//
// Created by mouse on 2019/10/8.
//

#include "app_config.h"

//class PortalRequestHandler : public AsyncWebHandler
//{
//public:
//    PortalRequestHandler()
//    {}
//
//    virtual ~PortalRequestHandler()
//    {}
//
//    bool canHandle(AsyncWebServerRequest *request)
//    {
//        return request->url().equals("/generate_204") || request->url().equals("/fwlink");
//    }
//
//    void handleRequest(AsyncWebServerRequest *request)
//    {
//        File file = SPIFFS.open(WEB_DIR_PATH + String("/portal.html"), "r");
//        if (!file)
//        {
//            AsyncResponseStream *response = request->beginResponseStream("text/html");
//            response->print("<!DOCTYPE html><html><head><title>genghe</title></head><body>");
//            response->print("<p>genghe system</p>");
//            response->printf("<p>http://%s%s</p>", request->host().c_str(), request->url().c_str());
//            response->printf("<p>进入<a href='http://%s'>配置</a>页</p>", WiFi.softAPIP().toString().c_str());
//            response->print("</body></html>");
//            request->send(response);
////            request->send(500, "application/json", R"({"error":"文件不存在"})");
//        }
//        request->send(200, "application/json", file.readString());
//
//        file.close();
//    }
//};

ConfigInfo *configInfo;

static File fsUploadFile;

void ConfigInfo::loadConfigFile(ConfigInfo *cfg)
{
    File configFile = SPIFFS.open(String(CONFIG_DIR_PATH) + "/config.json", "r");
    if (!configFile)
    {
        LOG_INFO("failed open file\r\n");
        return;
    }
    size_t size = configFile.size();
    std::unique_ptr<char[]> buf(new char[size]);
    configFile.readBytes(buf.get(), size);
    // 读取配置
    DynamicJsonDocument doc(2048);
    DeserializationError error = deserializeJson(doc, buf.get());
    if (error)
    {
        LOG_INFO("failed to load json config\r\n");
        return;
    }
    LOG_INFO("load config\r\n");
    strcpy(cfg->sta_wifi_ssid, doc["sta_wifi_ssid"]);
    strcpy(cfg->sta_wifi_password, doc["sta_wifi_password"]);
    strcpy(cfg->mqtt_server, doc["mqtt_server"]);
    strcpy(cfg->mqtt_port, doc["mqtt_port"]);
    strcpy(cfg->mqtt_username, doc["mqtt_username"]);
    strcpy(cfg->mqtt_password, doc["mqtt_password"]);
    cfg->info_version_code = doc["info_version_code"];
    if (!doc["device_key"].isNull())
    {
        strcpy(cfg->device_key, doc["device_key"]);
    }
    cfg->product_key_size = doc["product_key_size"];
    for (int i = 0; i < cfg->product_key_size; ++i)
    {
        if (!doc["product_keys"][i].isNull())
        {
            strcpy(cfg->product_keys[i], doc["product_keys"][i]);
        }
    }

    configFile.close();
}

void ConfigInfo::saveConfigFile(ConfigInfo *cfg)
{
    File configFile = SPIFFS.open(String(CONFIG_DIR_PATH) + "/config.json", "w+");
    if (!configFile)
    {
        LOG_INFO("failed open file\r\n");
        return;
    }
    DynamicJsonDocument doc(2048);
    doc["sta_wifi_ssid"] = cfg->sta_wifi_ssid;
    doc["sta_wifi_password"] = cfg->sta_wifi_password;
    doc["mqtt_server"] = cfg->mqtt_server;
    doc["mqtt_port"] = cfg->mqtt_port;
    doc["mqtt_username"] = cfg->mqtt_username;
    doc["mqtt_password"] = cfg->mqtt_password;
    doc["info_version_code"] = cfg->info_version_code;
    doc["device_key"] = cfg->device_key;
    doc["product_key_size"] = cfg->product_key_size;
    JsonArray productKeys = doc.createNestedArray("product_keys");
    for (int i = 0; i < cfg->product_key_size; ++i)
    {
        productKeys.add(cfg->product_keys[i]);
    }

    serializeJson(doc, configFile);

    configFile.close();
}

void ConfigInfo::printConfigInfo()
{
    LOG_DEBUG("==============================================\r\n");
    LOG_DEBUG("sta_wifi_ssid:%s\r\n", configInfo->sta_wifi_ssid);
    LOG_DEBUG("sta_wifi_password:%s\r\n", configInfo->sta_wifi_password);
    LOG_DEBUG("mqtt_server:%s\r\n", configInfo->mqtt_server);
    LOG_DEBUG("mqtt_port:%s\r\n", configInfo->mqtt_port);
    LOG_DEBUG("mqtt_username:%s\r\n", configInfo->mqtt_username);
    LOG_DEBUG("mqtt_password:%s\r\n", configInfo->mqtt_password);
    LOG_DEBUG("info_version_code:%d\r\n", configInfo->info_version_code);
    LOG_DEBUG("device_key:%s\r\n", configInfo->device_key);
    LOG_DEBUG("product_key_size:%d\r\n", configInfo->product_key_size);
    for (int i = 0; i < configInfo->product_key_size; ++i)
    {
        LOG_DEBUG("product_keys[%d]:%s\r\n", i, configInfo->product_keys[i]);
    }
    LOG_DEBUG("==============================================\r\n");
}

static void handleNotFound(AsyncWebServerRequest *request)
{
    LOG_DEBUG("NOT_FOUND: ");
    if (request->method() == HTTP_GET)
        LOG_DEBUG("GET");
    else if (request->method() == HTTP_POST)
        LOG_DEBUG("POST");
    else if (request->method() == HTTP_DELETE)
        LOG_DEBUG("DELETE");
    else if (request->method() == HTTP_PUT)
        LOG_DEBUG("PUT");
    else if (request->method() == HTTP_PATCH)
        LOG_DEBUG("PATCH");
    else if (request->method() == HTTP_HEAD)
        LOG_DEBUG("HEAD");
    else if (request->method() == HTTP_OPTIONS)
        LOG_DEBUG("OPTIONS");
    else
        LOG_DEBUG("UNKNOWN");
    LOG_DEBUG(" http://%s%s\r\n", request->host().c_str(), request->url().c_str());

    if (request->contentLength())
    {
        LOG_DEBUG("_CONTENT_TYPE: %s\r\n", request->contentType().c_str());
        LOG_DEBUG("_CONTENT_LENGTH: %u\r\n", request->contentLength());
    }

    int headers = request->headers();
    int i;
    for (i = 0; i < headers; i++)
    {
        AsyncWebHeader *h = request->getHeader(i);
        LOG_DEBUG("_HEADER[%s]: %s\r\n", h->name().c_str(), h->value().c_str());
    }

    int params = request->params();
    for (i = 0; i < params; i++)
    {
        AsyncWebParameter *p = request->getParam(i);
        if (p->isFile())
        {
            LOG_DEBUG("_FILE[%s]: %s, size: %u\r\n", p->name().c_str(), p->value().c_str(), p->size());
        } else if (p->isPost())
        {
            LOG_DEBUG("_POST[%s]: %s\r\n", p->name().c_str(), p->value().c_str());
        } else
        {
            LOG_DEBUG("_GET[%s]: %s\r\n", p->name().c_str(), p->value().c_str());
        }
    }

    request->send(404);
}

static void handleFileUpload(AsyncWebServerRequest *request,
                             const String &filename,
                             size_t index,
                             uint8_t *data,
                             size_t len,
                             bool final)
{
    if (!index)
    {
        //文件开始上传
        String dirPath = request->getParam("dirPath")->value();
        String name = filename;
        if (!name.startsWith("/"))
        {
            name = "/" + name;
        }
        LOG_INFO("upload file:%s%s\r\n", dirPath.c_str(), name.c_str());
        fsUploadFile = SPIFFS.open(dirPath + name, "w");
        LOG_DEBUG("upload begin\r\n");
    }

    //文件开始写入文件
    if (fsUploadFile)
    {
        //写入文件
        LOG_DEBUG("write:%dB\r\n", len);
        fsUploadFile.write(data, len);
        LOG_DEBUG("uploading...\r\n");
    }

    if (final)
    {
        //文件上传结束
        if (fsUploadFile)
        {
            fsUploadFile.close();
        }
        LOG_DEBUG("upload finished\r\n");
    }
}

static void handleRequestBody(AsyncWebServerRequest *request,
                              uint8_t *data,
                              size_t len,
                              size_t index,
                              size_t total)
{
    if (!index)
    {
        //数据开始更新
        String filePath = request->getParam("filePath")->value();
        LOG_INFO("update file:%s\r\n", filePath.c_str());
        fsUploadFile = SPIFFS.open(filePath, "w");
        LOG_DEBUG("update begin\r\n");
    }

    //数据开始写入文件
    if (fsUploadFile)
    {
        //写入文件
        LOG_DEBUG("write:%dB\r\n", len);
        fsUploadFile.write(data, len);
        LOG_DEBUG("updating...\r\n");
    }
    //数据更新结束
    if (index + len == total)
    {
        fsUploadFile.close();
    }
    LOG_DEBUG("update finished\r\n");
}


ConfigServer::ConfigServer()
{
    _webServer = new AsyncWebServer(80);
    _dnsServer = new AsyncDNSServer();
}

AsyncWebServer *ConfigServer::getWebServer()
{
    return _webServer;
}

AsyncDNSServer *ConfigServer::getDNSServer()
{
    return _dnsServer;
}

void ConfigServer::start()
{
    // 重启设备
    _webServer->on("/devices/reset",
                   HTTP_POST,
                   [](AsyncWebServerRequest *request) {

                       request->send(200, "application/json", R"({})");

                       ESP.reset();
                   }
    );

    // 查看全部文件
    _webServer->on("/files",
                   HTTP_GET,
                   [](AsyncWebServerRequest *request) {

                       DynamicJsonDocument doc(2048);
                       JsonArray fileList = doc.createNestedArray("fileList");

                       Dir dir = SPIFFS.openDir("/");
                       while (dir.next())
                       {
                           JsonObject fileDoc = fileList.createNestedObject();
                           fileDoc["fileName"] = dir.fileName();
                           fileDoc["fileSize"] = dir.fileSize();
                       }

                       String output = "";
                       serializeJson(doc, output);

                       request->send(200, "application/json", output);
                   }
    );

    // 上传文件
    _webServer->on("/files",
                   HTTP_PUT,
                   [](AsyncWebServerRequest *request) {
                       request->send(200, "application/json", R"({})");
                   },
                   handleFileUpload
    );

    // 删除文件
    _webServer->on("/files",
                   HTTP_DELETE,
                   [](AsyncWebServerRequest *request) {

                       if (!request->hasArg("filePath"))
                       {
                           request->send(500, "application/json", R"({"error":"缺少参数"})");
                       }
                       String filePath = request->arg("filePath");

                       if (!SPIFFS.exists(filePath))
                       {
                           request->send(500, "application/json", R"({"error":"文件不存在"})");
                       }

                       if (SPIFFS.remove(filePath))
                       {
                           request->send(200, "application/json", R"({})");
                       } else
                       {
                           request->send(500, "application/json", R"({"error":"删除失败"})");
                       }

                   }
    );

    // 查看文件内容
    _webServer->on("/file/contents",
                   HTTP_GET,
                   [](AsyncWebServerRequest *request) {

                       if (!request->hasArg("filePath"))
                       {
                           request->send(500, "application/json", R"({"error":"缺少参数"})");
                       }
                       String filePath = request->arg("filePath");

                       File file = SPIFFS.open(filePath, "r");
                       if (!file)
                       {
                           request->send(500, "application/json", R"({"error":"文件不存在"})");
                       }
                       request->send(200, "application/json", file.readString());

                       file.close();
                   }
    );

    // 更新文件内容
    _webServer->on("/file/contents",
                   HTTP_PUT,
                   [](AsyncWebServerRequest *request) {

                       request->send(200, "application/json", R"({})");
                   },
                   NULL,
                   handleRequestBody
    );

    // 查看全部wifi
    _webServer->on("/wifis/found",
                   HTTP_GET,
                   [](AsyncWebServerRequest *request) {

                       if (!request->hasArg("showHidden"))
                       {
                           request->send(500, "application/json", R"({"error":"缺少参数"})");
                       }
                       bool showHidden = request->arg("showHidden");

                       WiFi.scanNetworksAsync([=](int networksFound) {

                           DynamicJsonDocument doc(2048);
                           JsonArray wifiList = doc.createNestedArray("wifiList");

                           for (int i = 0; i < networksFound; i++)
                           {
                               if (WiFi.SSID(i).equals(""))
                                   continue;

                               JsonObject wifiDoc = wifiList.createNestedObject();
                               wifiDoc["ssid"] = WiFi.SSID(i);
                               wifiDoc["rssi"] = WiFi.RSSI(i);
                               if (WiFi.encryptionType(i) == ENC_TYPE_NONE)
                                   wifiDoc["encryptionType"] = "open";
                               if (WiFi.encryptionType(i) == ENC_TYPE_TKIP)
                                   wifiDoc["encryptionType"] = "tkip";
                               else if (WiFi.encryptionType(i) == ENC_TYPE_CCMP)
                                   wifiDoc["encryptionType"] = "ccmp";
                               else if (WiFi.encryptionType(i) == ENC_TYPE_WEP)
                                   wifiDoc["encryptionType"] = "wep";
                               else
                                   wifiDoc["encryptionType"] = "unknown";
                           }
                           String output = "";
                           serializeJson(doc, output);

                           request->send(200, "application/json", output);
                       }, showHidden);

                   }
    );

    _webServer->onNotFound(handleNotFound);

    _webServer->serveStatic("/", SPIFFS, WEB_DIR_PATH).setDefaultFile("index.html");

    _dnsServer->setErrorReplyCode(AsyncDNSReplyCode::NoError);
    bool op = _dnsServer->start(53, DOMAIN_NAME, WiFi.softAPIP());
    if (op)
    {
        LOG_INFO("start dnsserver success\r\n");
    } else
    {
        LOG_INFO("start dnsserver failed\r\n");
    }

//    _webServer->addHandler(new PortalRequestHandler()).setFilter(ON_AP_FILTER);

    _webServer->begin();
}

void ConfigServer::stop()
{
    _dnsServer->stop();
    _webServer->end();
}

ConfigTask::ConfigTask(bool formatFileSystem, bool configMode)
{
    this->formatFileSystem = formatFileSystem;
    this->configMode = configMode;

    if (this->configMode)
    {
        _configServer = new ConfigServer();
    }

    initConfig();
}

void ConfigTask::setupConfigMode()
{
    // 设置WIFI模式
    WiFi.mode(WIFI_AP);

    // WIFI热点配置
    IPAddress ip = IPAddress();
    IPAddress gw = IPAddress();
    IPAddress sn = IPAddress();
    ip.fromString(AP_STATIC_IP);
    gw.fromString(AP_STATIC_GW);
    sn.fromString(AP_STATIC_SN);
    WiFi.softAPConfig(ip, gw, sn);

    // 启动WIFI热点
    bool op = WiFi.softAP(AP_SSID);
    if (!op)
    {
        LOG_INFO("setup config mode failed, will reset...\r\n");
        //重启
        delay(1000);
        ESP.reset();
    }

    LOG_INFO("Soft-AP SSID = %s\r\n", WiFi.softAPSSID().c_str());
    LOG_INFO("Soft-AP PSK = %s\r\n", WiFi.softAPPSK().c_str());
    LOG_INFO("Soft-AP IP = %s\r\n", WiFi.softAPIP().toString().c_str());
    LOG_INFO("Soft-AP MAC = %s\r\n", WiFi.softAPmacAddress().c_str());
}

void ConfigTask::setupStaMode()
{
    // 设置WIFI模式
    WiFi.mode(WIFI_STA);
}

void ConfigTask::initConfig()
{
    if (formatFileSystem)
    {
        LOG_INFO("manual format file system\r\n");
        SPIFFS.format();
    }

    // 挂载文件系统
    LOG_INFO("mounted file system\r\n");
    if (!SPIFFS.begin())
    {
        LOG_INFO("format file system\r\n");
        SPIFFS.format();
    }

    // 初始化配置信息
    configInfo = new ConfigInfo;

    // 创建文件
    if (!SPIFFS.exists(String(CONFIG_DIR_PATH) + "/config.json"))
    {
        LOG_INFO("create config file\r\n");
        ConfigInfo::saveConfigFile(configInfo);
    }

    // 读取文件
    LOG_INFO("read config file\r\n");
    ConfigInfo::loadConfigFile(configInfo);
    ConfigInfo::printConfigInfo();
}

void ConfigTask::initNetwork()
{
    if (configMode)
    {
        LOG_INFO("Enter config mode\r\n");
        setupConfigMode();
        // 启动配置web服务
        _configServer->start();
    } else
    {
        LOG_INFO("Enter sta mode\r\n");
        setupStaMode();
    }
}

namespace WifiUtils {

    wl_status_t wifiConnect(String ssid, String password, uint16_t timeout)
    {
        if (WiFi.status() == WL_CONNECTED)
        {
            LOG_INFO("already connected\r\n");
            return WL_CONNECTED;
        }

        uint16_t count = timeout;
        //扫描并连接WiFi
        if (ssid == "")
            if (WiFi.SSID())
            {
                ETS_UART_INTR_DISABLE();
                wifi_station_disconnect();
                ETS_UART_INTR_ENABLE();
                WiFi.begin();
            } else
            {
                LOG_INFO("\r\nno saved credentials");
            }
        else
            WiFi.begin(ssid, password);

        LOG_INFO("\r\nConnecting to %s ...", ssid.c_str());
        while (timeout == 0 || count-- > 0)
        {
            if (WiFi.status() == WL_CONNECTED)
            {
                LOG_INFO("\r\nconnection successed");
                break;
            }
            if (WiFi.status() == WL_CONNECT_FAILED)
            {
                LOG_INFO("\r\nconnection failed");
                break;
            }
            if (timeout != 0 && count <= 0)
            {
                LOG_INFO("\r\nconnection timed out");
                break;
            }
            delay(1000);
            LOG_INFO(".");
        }
        LOG_INFO("\r\n");
        return WiFi.status();
    }

    void printWifi()
    {
        LOG_INFO("====================================\r\n");
        LOG_INFO("Chip ID: %d\r\n", ESP.getChipId());
        LOG_INFO("Flash Chip ID: %d\r\n", ESP.getFlashChipId());
        LOG_INFO("Flash Chip Size: %d\r\n", ESP.getFlashChipSize());
        LOG_INFO("Flash Chip RealSize: %d\r\n", ESP.getFlashChipRealSize());
        LOG_INFO("ESP8266 mode is: %d\r\n", WiFi.getMode());
        LOG_INFO("WiFi AP IP is: %s\r\n", WiFi.softAPIP().toString().c_str());
        LOG_INFO("WiFi AP MAC address is: %s\r\n", WiFi.softAPmacAddress().c_str());
        LOG_INFO("WiFi STA IP is: %s\r\n", WiFi.localIP().toString().c_str());
        LOG_INFO("WiFi STA MAC address is: %s\r\n", WiFi.macAddress().c_str());
        LOG_INFO("====================================\r\n");
    }

}

